#include <click/config.h>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/glue.hh>
#include <click/straccum.hh>
#include <clicknet/ether.h>
#include <clicknet/llc.h>
#include <clicknet/wifi.h>
#include "moreflow.hh"

CLICK_DECLS

#define ATHDESC

static void _wifiSetRate(WritablePacket *p, int rate, int tries) {
    click_wifi_extra *ceh = (click_wifi_extra *) p->all_user_anno();
    ceh->magic = WIFI_EXTRA_MAGIC;
    ceh->rate = rate;
#ifdef ATHDESC
    ceh->max_tries = tries + 1;
#else
    ceh->max_tries = tries;
#endif
}


void MOREFlow::set_outflow(Vector<float> &zp) {
    for(int d = 0; d < zp.size(); ++d)
        _outflow[d] = zp[d];
    for(int d = zp.size(); d < MAX_DST; ++d)
        _outflow[d] = 0.0f;
}

void MOREFlow::add_inflow(Vector<float> &zp) {
    float* inflow = new float[MAX_DST];
    for(int d = 0; d < zp.size(); ++d)
        inflow[d] = zp[d];
    for(int d = zp.size(); d < MAX_DST; ++d)
        inflow[d] = 0.0f;
    _inflows.push_back(inflow);
}

static
void maximal(float &v, const float& c) {
    if(v < c) v = c;
}

/**
 * Multicast credit factor update
 */
bool MOREFlow::update_dstmap(uint32_t newmap) {
    if(_dstmap != newmap) {
        _dstmap &= newmap;

        if(_dstmap == 0) {
            creditf = 0;
            if(_batch)
                _batch->_credit = 0;
        } else if(bcfg.rate > 0) {
            // recompute creditf
            int num_teachers = _inflows.size();
            float maxrxrate[num_teachers]; 
            for(int t = 0; t < num_teachers; ++t) {
                maxrxrate[t] = 0.0f;
            }
            float maxtxrate = 0.0f;
            for(int d = 0; d < MAX_DST; ++d) {
                // find max rates across destinations
                bool dstactive = (((1 << d) & _dstmap) != 0);
                if(dstactive) {
                    for(int t = 0; t < num_teachers; ++t) {
                        maximal(maxrxrate[t], _inflows[t][d]);
                    }
                    maximal(maxtxrate, _outflow[d]);
                }
            }
            float totalrxrate = 0.0f;
            for(int t = 0; t < num_teachers; ++t) {
                // aggregate rx rate
                totalrxrate+= maxrxrate[t];
            }
            if(totalrxrate > 0)
                creditf = maxtxrate / totalrxrate;
            else
                creditf = 0.0;
        }
        return true;
    }
    return false;
}

MOREBatch *MOREFlow::create(uint seqno) {
    // TODO we should ask EOTXMetric for new values
    assert(_batch == 0);
    MOREBatch *batch = new MOREBatch(seqno, bcfg);
    _batch = batch;
    return batch;
}

void MOREFlow::source_batch() {
    assert(_batch == 0 || _dstmap == 0);
    if(_batch) {
        delete _batch;
        _batch = 0;
    }
    ++_seqno;
    if(DEBUG) click_chatter("sourcing batch seqno= %d\n", _seqno);

    assert(bcfg.k > 0);
    MOREBatch *batch = create(_seqno);
    for(int i = 0; i < bcfg.k; ++i) {
        // FIXME: we'll create real packets later
        WritablePacket *p = Packet::make(
                MORE_HEADROOM
                + sizeof(more_pkt)
                + sizeof(more_data)
                + sizeof(more_block)
                + bcfg.k
                + sizeof(uint16_t),     // word for data length
                0, bcfg.datasize - sizeof(uint16_t), 0);
        batch->source(p);
    }
    _dstmap = ~0; // so that update actually does something
    update_dstmap(src_dstmap); // to recompute creditf
    // source never runs out of credit
    batch->_credit = 1e6; 
}

void MOREFlow::flush(uint cum_ack) {
    if(cum_ack <= _seqno) return;

    _seqno = cum_ack;
    _dstmap = (uint32_t)-1; // all ones
    if(_batch) {
        delete _batch;
        _batch = 0;
    }
    if(DEBUG) click_chatter("SEQNO %d\n", _seqno);
}

MOREBatch *MOREFlow::lookup(uint seqno, bool force) {
    if(_batch) {
        assert(_batch->seqno() == seqno);
        return _batch;
    }
    if(force) {
        if(DEBUG) click_chatter("NEW Batch %u\n", seqno);
        return create(seqno);
    }
    return 0;
}


WritablePacket *MOREFlow::encode() {
    MOREBatch *batch = _batch;
    if(!queued())
        // oops, nothing to do
        return 0;

    WritablePacket *p = batch->take();
    assert(p);

    batch->_credit-= 1.0; 
    p = p->push(sizeof(more_pkt));
    more_pkt *mp = (more_pkt *)p->data();
    mp->_dstmap = _dstmap;
    mp->_batch = batch->seqno();
    p = p->push(sizeof(click_ether));
    click_ether *eh = (click_ether *)p->data();
    memset(eh->ether_dhost, 0xFF, 6);
    _wifiSetRate(p, batch->c().rate, 1);

    return p;
}

// output an ACK for the flow
WritablePacket* MOREFlow::make_ack(){
    // all that matters is cum_ack and dstmap
    if(ackflag != PENDING)
        return 0;

    if(DEBUG_ACK)
        click_chatter("sending ACK for batch %d via %s",
                      seqno(), ack_next.s().c_str());
    assert(ack_rate > 0);

    WritablePacket *p = Packet::make(
                            MORE_HEADROOM
                            +sizeof(more_pkt)
                            +sizeof(click_ether),
                            0,0,0);

    p = p->push(sizeof(more_pkt));
    more_pkt *mp = (more_pkt *)p->data();
    mp->_dstmap = dstmap();
    mp->_batch = seqno();
    p = p->push(sizeof(click_ether));
    click_ether *eh = (click_ether *)p->data();

    memcpy(eh->ether_dhost, ack_next.sdata(), 6);
    _wifiSetRate(p, ack_rate, 1);

    ackflag = SENT; // we've sent it, let's wait for TXf
    return p;
}

#include <click/dequeue.cc>
// template class declarations here
CLICK_ENDDECLS
ELEMENT_PROVIDES(MOREFlow)

